다음 커맨드를 입력하여 commitlint를 설치한다.
@commitlint/config-conventional
패키지는 conventional commit의 rule을 적용시킨 프리셋 설정 이다.
# npm
npm install -D @commitlint/cli @commitlint/config-conventional
# yarn
yarn add -D @commitlint/cli @commitlint/config-conventional
그리고 프로젝트 루트에 commitlint.config.js
를 생성하고 아래를 입력한다.
module.exports = {
extends: ['@commitlint/config-conventional'],
}
설치가 됐으면 cli에서 간단하게 테스트 해볼 수 있다.
echo 'foo: bar' | commitlint
⧗ input: foo: bar
✖ type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test] [type-enum]
✖ found 1 problems, 0 warnings
ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint
yarn commitlint
등으로 실행해볼 수 있다.https://commitlint.js.org/#/reference-configuration
규칙은 메세지를 파싱한 뒤에 메세지에 어떤 요소가 파싱되었는지에 따라 조건을 통해 pass/fail로 결정난다.
때문에 파싱을 어떻게 할건지에 대해서도 설정이 가능하다.
커밋 메세지의 가장 윗줄에 feat(core): (KE-1234) subject
와 같은 형태로 메세지를 파싱하도록 적용해보자.
module.exports = {
extends: ['@commitlint/config-conventional'],
parserPreset: {
parserOpts: {
headerPattern: /^(\w*)(?:\((\w*)\))?:\s?(?:\((\w+-\d+)\)\s)?(.*)$/,
headerCorrespondence: ['type', 'scope', 'ticket', 'subject'],
},
}
}
headerPattern
설정 값에 규칙을 정규식으로 표현할 수 있다.
headerCorrespondence:
에는 각 캡쳐그룹이 헤더의 어떤 부분을 인식할 것인지 지정할 수 있다.(토큰)
설명으로 풀어보자
(\w*)
는 word를 캡쳐하고 이는 type에 대응된다. 예) feat, docs, fix, ...(?:\((\w*)\))?
\( * \) 안의 (\w*)는 word를 캡쳐하고 scope에 대응된다. 예) (core), (table)
다음 :\s 세미콜론 다음 공백이 들어오거나 없는 형태
다음 (?:\((\w+-\d+)\)\s)?
마찬가지로 바깥 괄호는 선택적으로 인식하기 위해 그룹하는 용도이다
(.*)$
는 아무 내용이 올 수 있다. subject에 대응된다. 예) 제목입니다자세한 사항은 여기를 참조: https://commitlint.js.org/#/reference-rules
각 규칙은 다음 3가지 값을 갖는 배열로 설정되어야한다. (혹은 3가지 값을 갖는 배열을 반환하는 함수여도 된다)
[0..2]
: 0
해제, 1 경고(알림), 2 에러(커밋 불가) always|never
: never로 설정할 경우 규칙을 반대로 적용한다.위의 파싱 설정을 한 경우 규칙대로 캡쳐된 값을 룰셋에 넘겨줄 수 있다.
ticket이라는 토큰에 대해 commitlint는 알지 못하기 때문에 이를 강제하거나 경고하기 위해서는 별도의 rule을 추가해줘야한다.
커밋 메세지에 ticket이 없을때를 대응하는 커스텀 룰을 하나 만들어보자.
module.exports = {
//...
plugins: [
{
rules: {
'ticket-empty': parsed => {
cosnt {ticket} = parsed; // 이런식으로 위에서 지정한 토큰에 대응되는 부분을 가져온다
if (ticket === null) {
return [
false, // false일 경우 linting 통과하지 못한 것이다.
'이슈 번호가 없습니다!' // 적절한 경고 문구를 전달한다.
]
}
return [true, ''] // 조건 통과의 경우
}
}
}
],
rules: {
'ticket-empty': [1, 'always'] // 위에서 만든 룰 적용
}
}